package lion.dev.lang;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;

import lion.dev.text.Formater;
import lion.dev.web.Validator;
import lion.json.Json;
import lion.json.JsonParser;

import org.apache.commons.lang.StringUtils;

/**
 * 封装了LinkedHashMap,提供了许多便利的方法。
 *
 * @author lion
 * @mail hl_0074@sina.com
 * @date 2011-9-14
 */
public class MapJ extends LinkedHashMap<String, Object> {

	private static final long serialVersionUID = 7849118028760898789L;

	@SuppressWarnings("unchecked")
	public <E> E getE(String key) {

		return (E) this.get(key);
	}

	public Number getNumber(String key, Number deft) {

		Object o = this.get(key);
		Number result = -1;
		if (o != null) {
			if (o instanceof Number) {
				result = (Number) o;
			} else if (o instanceof Character) {
				result = Character.getNumericValue((Character) o);
			} else if (o instanceof String) {
				result = Validator.toNumber(String.valueOf(o), -1);
			}
		} else {
			result = deft;
		}
		return result;
	}

	public Number getNumber(String key) {

		return this.getNumber(key, -1);
	}

	public int getInt(String key) {

		return this.getNumber(key).intValue();
	}

	public int getInt(String key, int deft) {

		return this.getNumber(key, deft).intValue();
	}

	public long getLong(String key) {

		return this.getNumber(key).longValue();
	}

	public long getLong(String key, long deft) {

		return this.getNumber(key, deft).longValue();
	}

	public float getFloat(String key) {

		return this.getNumber(key).floatValue();
	}

	public float getFloat(String key, double deft) {

		return this.getNumber(key, deft).floatValue();
	}

	public double getDouble(String key) {

		return this.getNumber(key).doubleValue();
	}

	public double getDouble(String key, double deft) {

		return this.getNumber(key, deft).doubleValue();
	}

	public byte getByte(String key) {

		return this.getNumber(key).byteValue();
	}

	public byte getByte(String key, byte deft) {

		return this.getNumber(key, deft).byteValue();
	}

	public short getShort(String key) {

		return this.getNumber(key).shortValue();
	}

	public short getShort(String key, short deft) {

		return this.getNumber(key, deft).shortValue();
	}

	public boolean getBoolean(String key) {

		return this.getBoolean(key, false);
	}

	public boolean getBoolean(String key, boolean deft) {

		Object o = this.get(key);
		boolean result = false;

		if (o != null) {
			String tmp = String.valueOf(o);
			if (o instanceof Boolean) {
				result = Boolean.parseBoolean(tmp);
			} else if ("true".equals(tmp) || "1".equals(tmp) || "on".equals(tmp)) {
				result = true;
			}
		} else {
			result = deft;
		}
		return result;

	}

	public Date getDate(String key) {

		return this.getDate(key, null, null);
	}

	public Date getDate(String key, Date deft) {

		return this.getDate(key, null, deft);

	}

	public Date getDate(String key, String pattern) {

		return this.getDate(key, pattern, null);
	}

	public Date getDate(String key, String pattern, Date deft) {

		Object o = this.get(key);

		Date result = null;

		if (o != null) {

			if (o instanceof Date) {
				result = (Date) o;
			} else {
				String tmp = String.valueOf(o);
				if (StringUtils.isBlank(pattern)) {
					result = Formater.parseDate(tmp);
				} else {
					result = Formater.parseDate(pattern, tmp);
				}
			}
		} else {
			result = deft;
		}
		return result;
	}

	public String getString(String key) {

		Object o = this.get(key);

		String result = null;
		if (o != null) {
			if (o instanceof String) {
				result = (String) o;
			} else {
				result = String.valueOf(o);
			}
		}

		return result;
	}

	public String getString(String key, String deft) {

		String result = this.getString(key);
		if (result == null) {
			result = deft;
		}
		return result;
	}

	public String[] getStringArray(String key) {

		return getStringArray(key, ",");
	}

	public String[] getStringArray(String key, String split) {

		String s = this.getString(key, "");
		if (StringUtils.isEmpty(split)) {
			split = ",";
		}

		String[] arr = s.split(Matcher.quoteReplacement(split));
		List<String> list = new ArrayList<>();
		for (String a : arr) {
			if (StringUtils.isNotBlank(a)) {
				list.add(a);
			}
		}

		return list.toArray(new String[0]);
	}

	public <T> T getE(int index) {

		if (index >= this.size()) { return null; }

		String[] keys = this.keySet().toArray(new String[0]);

		return this.getE(keys[index]);
	}

	public String getKey(int index) {

		if (index >= this.size()) { return null; }

		String[] keys = this.keySet().toArray(new String[0]);

		return keys[index];
	}

	public static MapJ map(Map<String, ? extends Object> map) {

		MapJ mapJ = new MapJ();
		mapJ.putAll(map);

		return mapJ;
	}

	public synchronized static MapJ mapRequest(Map<String, String[]> parameter) {

		MapJ mapJ = new MapJ();

		for (String key : parameter.keySet()) {
			mapJ.put(key, StringUtils.join(parameter.get(key), ","));
		}
		return mapJ;
	}

	public synchronized static MapJ mapBean(Object o) {

		MapJ mapJ = new MapJ();

		Field[] fields = o.getClass().getDeclaredFields();
		for (Field f : fields) {

			if (Modifier.isStatic(f.getModifiers())) {
				continue;
			}

			Object value = getFieldValue(o, f);

			mapJ.put(f.getName(), value);
		}

		return mapJ;
	}

	/**
	 * use public get method first ,field second
	 */
	public synchronized static MapJ mapBean(Object o, String[] fields) {

		MapJ mapJ = new MapJ();

		for (String field : fields) {
			Field f = null;
			try {
				f = o.getClass().getDeclaredField(field);
			} catch (Exception e) {
			}
			Object value = getFieldValue(o, f);

			mapJ.put(field, value);
		}

		return mapJ;
	}

	public static MapJ fromJson(String json) {

		return new JsonParser(json).parse();
	}

	private static Object getFieldValue(Object o, Field f) {

		Object value = null;

		if (f == null || o == null) { return null; }

		f.setAccessible(true);
		Method m = null;

		String methodName = "get" + StringUtils.capitalize(f.getName());
		if (f.getType().equals(Boolean.TYPE)) {
			if (f.getName().startsWith("is") && Character.isUpperCase(f.getName().charAt(2))) {
				methodName = f.getName();
			} else {
				methodName = "is" + StringUtils.capitalize(f.getName());
			}
		}

		try {
			m = o.getClass().getMethod(methodName, new Class[0]);
		} catch (Exception e1) {
			e1.printStackTrace();
		}

		if (m == null || Modifier.isStatic(m.getModifiers()) || !Modifier.isPublic(m.getModifiers())) { return null; }

		m.setAccessible(true);
		try {
			value = m.invoke(o, new Object[0]);
		} catch (Exception e) {
		}
		if (value == null) {
			try {
				value = f.get(o);
			} catch (Exception e) {
			}
		}

		return value;
	}

	public static MapJ mapProperties(String file) {

		MapJ result = new MapJ();
		File f = new File(file);
		if (f.exists()) {
			Properties prop = new Properties();
			try {
				prop.load(new FileInputStream(f));
			} catch (Exception e) {
			}
			for (String key : prop.stringPropertyNames()) {
				result.put(key, prop.get(key));
			}
		}
		return result;
	}

	public String toJSON() {

		return Json.toJson(this);
	}

	@Override
	public Object put(String key, Object value) {

		return super.put(key, value);
	}

	@Override
	public void putAll(Map<? extends String, ? extends Object> m) {

		super.putAll(m);
	}

	@Override
	public Object remove(Object key) {

		return super.remove(key);
	}

}
